package com.boot.security.service.authentication;

import com.boot.security.model.bo.TokenAuthentication;
import com.boot.security.model.vo.AccountInfo;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.log.LogMessage;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.session.SessionAuthenticationException;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author 霜寒 <1621856595@qq.com>
 * @description 此过滤器检查请求是否携带凭据
 * @date 2020/2/14 17:45
 **/
@Slf4j
public class TokenFilter extends GenericFilterBean {

    private final RequestMatcher ignore;
    private final TokenService tokenService;

    public TokenFilter(RequestMatcher ignore, TokenService tokenService) {
        this.ignore = ignore;
        this.tokenService = tokenService;
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        doFilter((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, filterChain);
    }

    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        if (ignoreRequest(request)) {
            chain.doFilter(request, response);
        } else {
            log.info("======================================== TokenFilter =======================================");
            String token = request.getHeader(TokenService.AUTHORIZATION);
            String uri = request.getRequestURI();
            String method = request.getMethod();
            // 是否携带 Token
            if (!StringUtils.hasText(token)) {
                throw new SessionAuthenticationException(String.format(
                        "未携带凭据 URI：%s , METHOD: %s", uri, method
                ));
            } else {
                log.debug("携带token: {}", token);
                Claims claims = tokenService.parse(token);
                // Token 是否过期
                if (tokenService.isNotExpired(claims)) {
                    // Token 是否需要更新
                    if (tokenService.isNeedUpdate(claims)) {
                        response.addHeader(TokenService.UPGRADE, String.valueOf(true));
                    }
                    AccountInfo info = tokenService.getAccountInfo(claims);
                    TokenAuthentication authentication = new TokenAuthentication(info);
                    authentication.setAuthenticated(true);
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                    log.info("用户 {} 访问了 URI：{} , METHOD: {}", authentication.getName(), uri, method);
                    chain.doFilter(request, response);
                } else {
                    throw new AccountExpiredException(String.format(
                            "此凭据已过期 URI：%s , METHOD: %s", uri, method
                    ));
                }
            }
        }
    }

    protected boolean ignoreRequest(HttpServletRequest request) {
        if (ignore.matches(request)) {
            if (this.logger.isTraceEnabled()) {
                this.logger.trace(LogMessage.format("request match to %s , ignore", ignore));
            }
            return true;
        }
        return false;
    }
}
